////////////////////////////////////////////////////////////////////////////////
// This file is part of the Mist compiler.                                    //
//                                                                            //
// Copyright (C) 2009 The Mist project                                        //
//                                                                            //
// For a list of contributors, see the COMMITTERS file.                       //
//                                                                            //
// The Mist compiler is free software: you can redistribute it and/or modify  //
// it under the terms of the GNU General Public License as published by       //
// the Free Software Foundation, either version 3 of the License, or          //
// (at your option) any later version.                                        //
//                                                                            //
// The Mist compiler is distributed in the hope that it will be useful,       //
// but WITHOUT ANY WARRANTY; without even the implied warranty of             //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the              //
// GNU General Public License for more details.                               //
//                                                                            //
// You should have received a copy of the GNU General Public License          //
// in the COPYING file. If not, see <http://www.gnu.org/licenses/>.           //
////////////////////////////////////////////////////////////////////////////////

///
/// \file
/// Contains the Computed class.
///

#ifndef COMMON_COMPUTED_HPP
#define COMMON_COMPUTED_HPP

////////////////////////////////////////////////////////////////////////////////
// Includes                                                                   //
////////////////////////////////////////////////////////////////////////////////

#include <boost/optional.hpp>
using boost::optional;

#include "common/assert.hpp"
#include "common/exception.hpp"

////////////////////////////////////////////////////////////////////////////////
// CircularDependency Exception Class                                          //
////////////////////////////////////////////////////////////////////////////////

DEFINE_EXCEPTION( CircularDependencyException, "A circular computational dependency has arisen." )

////////////////////////////////////////////////////////////////////////////////
// ComputedState Enum                                                         //
////////////////////////////////////////////////////////////////////////////////

/// A possible state of the value computation.
///
enum ComputedState {
	EMPTY,     ///< computation has not yet been attempted
	COMPUTING, ///< computation is in progress
	COMPUTED   ///< computation has taken place
};

////////////////////////////////////////////////////////////////////////////////
// Computed Class                                                             //
////////////////////////////////////////////////////////////////////////////////

/// A value that must be computed first, and which knows how to compute itself.
/// * It makes sure that a value is found once, and then remains constant.
/// * It will throw an exception if there is a cyclic dependency in the calculation.
/// * It can be specified that the value could not be found. This can be tested for.
///
template <class T>
class Computed {
	public:
		
		/// Construct an uncomputed value of type T.
		///
		Computed();
		
		/// \return the state of computation of this value
		///
		ComputedState state() const;
		
		/// \return  true, if there is an error in computing this value
		///         false, otherwise
		///
		/// This function lazily tries to compute the value, then checks
		/// if there was an error.
		///
		bool error();
		
		/// \return the value, if there is no error in computation
		///
		/// This function lazily tries to compute the value, then
		/// returns the value if one was found.
		///
		/// \throws CyclicDependencyException if a cyclic dependency was discovered
		/// \pre !error()
		///
		T value();
		
		/// \return the value, if there is no error in computation
		///
		/// The call-operator lazily tries to compute the value, then
		/// returns the value if one was found.
		///
		/// \throws CyclicDependencyException if a cyclic dependency was discovered
		/// \pre !error()
		///
		T operator()();
	
	protected:
		
		virtual void compute_value();
		
		void start_computing();
		
		void set_value(T value);
		
		void set_error();
	
	private:
		
		void lazy_compute();
		
		ComputedState _state;
		optional<T>   _value;
};

////////////////////////////////////////////////////////////////////////////////
// Template Function Implementations                                          //
////////////////////////////////////////////////////////////////////////////////

template <class T>
Computed<T>::Computed() : _state(EMPTY) {
} // intentionally empty

template <class T>
ComputedState Computed<T>::state() const {
	return _state;
}

template <class T>
bool Computed<T>::error() {
	try {
		lazy_compute();
	} catch (CircularDependencyException) {
		set_error();
	}
	
	return !bool(_value);
}

template <class T>
T Computed<T>::value() {
	assert(!error());
	
	lazy_compute();
	
	return *_value;
}

template <class T>
T Computed<T>::operator()() {
	assert(!error());
	
	return value();
}

//// protected ////////////////////////////////////

template <class T>
void Computed<T>::compute_value() {
	// This function should be overloaded by
	// subclasses if it's ever even called. So:
	//
	set_error();
} // intentionally empty

template <class T>
void Computed<T>::start_computing() {
	assert(state() != COMPUTED);
	
	if (state() == COMPUTING) {
		_state = COMPUTED;
		throw CircularDependencyException();
	}
	
	_state = COMPUTING;
}

template <class T>
void Computed<T>::set_value(T value) {
	assert(state() != COMPUTED);
	
	_value = value;
	_state = COMPUTED;
}

template <class T>
void Computed<T>::set_error() {
	assert(state() != COMPUTED);
	
	_state = COMPUTED;
}

//// private ////////////////////////////////////

template <class T>
void Computed<T>::lazy_compute() {
	if (state() != COMPUTED) {
		try {
			start_computing();
		} catch (CircularDependencyException) {
			set_error();
			throw;
		}
		compute_value();
	}
	
	assert(state() == COMPUTED);
}

#endif // COMMON_COMPUTED_HPP
